package com.glasgowrax;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import org.json.JSONObject;

import android.app.Activity;
import android.graphics.Color;
import android.location.Address;
import android.location.Geocoder;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import com.glasgowrax.domain.CycleRacksGeoJson;
import com.glasgowrax.domain.Feature;
import com.glasgowrax.domain.LatLng;
import com.glasgowrax.domain.directions.Directions;
import com.glasgowrax.domain.directions.Leg;
import com.glasgowrax.domain.directions.Route;
import com.glasgowrax.domain.directions.Step;
import com.glasgowrax.interfaces.DownloadsDirections;
import com.glasgowrax.interfaces.DownloadsWalkingDirections;
import com.glasgowrax.task.DownloadDirectionsTask;
import com.glasgowrax.task.DownloadWalkingDirectionsTask;
import com.glasgowrax.util.FileUtil;
import com.glasgowrax.util.LocationUtil;
import com.glasgowrax.util.infrastructure.FeatureListTooSmallException;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.PolylineOptions;
import com.google.gson.Gson;

public class MapActivity extends Activity implements DownloadsDirections, DownloadsWalkingDirections {
	
	private static final String COLOR_CYCLE = "#8806C70C";
	private static final String COLOR_WALKING = "#66595959";
	private static final String locale = ", Glasgow, UK";
	
	public static final int MAX_GEOCODE_RESULTS = 5;
	public static final double BOUNDARY_LOWER_LEFT_LATITUDE = 50.0; //55
	public static final double BOUNDARY_LOWER_LEFT_LONGITUDE = -10.0; //-6
	public static final double BOUNDARY_UPPER_RIGHT_LATITUDE = 60.0; //57
	public static final double BOUNDARY_UPPER_RIGHT_LONGITUDE = 1.0; //-2
	
	private GoogleMap map;
	private CycleRacksGeoJson cycleRacksGeoJson;
	private EditText editTextFrom;
	private EditText editTextTo;
	private Address fromAddress;
	private Address toAddress;
	
	private String fromQuery;
	private String toQuery;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_map);
        
        fromQuery = "";
        toQuery = "";
        
        map = ((MapFragment) getFragmentManager().findFragmentById(R.id.fragment_map)).getMap();
        editTextFrom = (EditText) this.findViewById(R.id.edit_text_from);
        editTextTo = (EditText) this.findViewById(R.id.edit_text_to);
        
		editTextFrom.addTextChangedListener(new TextWatcher(){
	        public void afterTextChanged(Editable s) {}
	        public void beforeTextChanged(CharSequence s, int start, int count, int after){}
	        public void onTextChanged(CharSequence s, int start, int before, int count){
	        	String newQuery = s.toString().trim();
	        	if (!fromQuery.equals(newQuery)) {
	        		fromQuery = s.toString();
	        	}
	        }
	    });
		
		editTextTo.addTextChangedListener(new TextWatcher(){
	        public void afterTextChanged(Editable s) {}
	        public void beforeTextChanged(CharSequence s, int start, int count, int after){}
	        public void onTextChanged(CharSequence s, int start, int before, int count){
	        	String newQuery = s.toString().trim();
	        	if (!toQuery.equals(newQuery)) {
	        		toQuery = s.toString();
	        	}
	        }
	    });
        
        setUpMap();        
//        getDirections("G36DY", "55.86443,-4.269744");
    }   
	
    
	/**
     * Initialise Map settings
     */
    private void setUpMap() {
    	
    	com.google.android.gms.maps.model.LatLng glasgow = new com.google.android.gms.maps.model.LatLng(55.857292,-4.256859);
    	
    	map.setMyLocationEnabled(true);
        map.moveCamera(CameraUpdateFactory.newLatLngZoom(glasgow, 16));

        String assetJsonString = FileUtil.loadJSONFromAsset(this);
        Log.i("JSON", assetJsonString);
        
        Gson gson = new Gson();
		cycleRacksGeoJson = gson.fromJson(assetJsonString, CycleRacksGeoJson.class);
		
		addAllCycleRacksToMap();
		
//		Log.i("Features", "Size: "+cycleRacksGeoJson.getFeatures().size());
    }
    
    private void getDirections() {
    	
    	map.clear();

		if (fromAddress != null && toAddress != null) {		
 	
	    	List<Feature> racksByDistance = new ArrayList<Feature>();
			try {
				racksByDistance = LocationUtil.getNearestFeatureToTarget(3, cycleRacksGeoJson.getFeatures(), new LatLng(toAddress.getLatitude(), toAddress.getLongitude()));
			} catch (FeatureListTooSmallException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	    	String from = fromAddress.getLatitude()+","+fromAddress.getLongitude();
	    	String to = toAddress.getLatitude()+","+toAddress.getLongitude();	    	
	    	
	    	for (Feature rack : racksByDistance) {
	        	Log.i("RACK", ""+rack.getGeometry().getCoordinates()[1]+","+rack.getGeometry().getCoordinates()[0]);
	    		String rackPoint = rack.getGeometry().getCoordinates()[1]+","+rack.getGeometry().getCoordinates()[0];
	            DownloadDirectionsTask cycleDirectionsTask = new DownloadDirectionsTask(this, from, rackPoint, true, false, rack);
	            cycleDirectionsTask.execute();            
	            DownloadWalkingDirectionsTask walkingDirectionsTask = new DownloadWalkingDirectionsTask(this, rackPoint, to, true, true, rack);
	            walkingDirectionsTask.execute();
	            
		        map.addMarker(new MarkerOptions()
		            .title("Bike Rack!")
		            .icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_rack_06))
		            .anchor(0.5f, 0.5f)
		//            .snippet("The most populous city in Scotland.")
		            .position(new com.google.android.gms.maps.model.LatLng( rack.getGeometry().getCoordinates()[1],  rack.getGeometry().getCoordinates()[0])));
	    	}
		}
	}
    
    public void onClickSearch(View v) {
    	Log.i("SEARCH", fromQuery+" , "+toQuery);
    	if (fromQuery.length() > 0 && toQuery.length() > 0) {
        	GeocodingTask fromGeocodingTask = new GeocodingTask(true); // from
        	GeocodingTask toGeocodingTask = new GeocodingTask(false); // to
        	fromGeocodingTask.execute();
        	toGeocodingTask.execute();
    	}
    }
    
	@Override
	public void updateDisplayWithCycling(Feature rack, String result) {
		try {
			JSONObject jsonResult = new JSONObject(result);
			Directions directions = new Directions(jsonResult);
			
			for (Route r : directions.getRoutes()) {
				for (Leg l : r.getLegs()) {
					
					// Add START marker
					map.addMarker(new MarkerOptions()
				        .position(new com.google.android.gms.maps.model.LatLng(l.getStartPoint().latitude, l.getStartPoint().longitude))
			        	.icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_directions_black))
			        	.anchor(0.5f, 0.5f)
				        .title("Start"));
					
					for (int i=0; i<l.getSteps().size(); i++) {
						
						Step s = l.getSteps().get(i);
						
						List<LatLng> points = FileUtil.decodePolygonPoints(s.getPolyline());

						// Add rack as final point!
						if (i==l.getSteps().size()-1) {
							points.add(new LatLng(rack.getGeometry().getCoordinates()[1], rack.getGeometry().getCoordinates()[0]));
						}

						addCyclePolyline(points);
					}
				}
			}			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public void updateDisplayWithWalking(Feature rack, String result) {
		try {
			JSONObject jsonResult = new JSONObject(result);
			Directions directions = new Directions(jsonResult);
			
			for (Route r : directions.getRoutes()) {
				for (Leg l : r.getLegs()) {					
					
					// Add END marker
					map.addMarker(new MarkerOptions()
				        .position(new com.google.android.gms.maps.model.LatLng(l.getEndPoint().latitude, l.getEndPoint().longitude))
			        	.icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_directions_black))
			        	.anchor(0.5f, 0.5f)
				        .title("End"));
					
					List<LatLng> points = new ArrayList<LatLng>();
					// Add rack as first point!
					points.add(new LatLng(rack.getGeometry().getCoordinates()[1], rack.getGeometry().getCoordinates()[0]));
					
					for (int i=0; i<l.getSteps().size(); i++) {	
						
						Step s = l.getSteps().get(i);						
						points.addAll(FileUtil.decodePolygonPoints(s.getPolyline()));
						
						addWalkingPolyline(points);
					}
				}
			}			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private void addCyclePolyline(List<LatLng> points) {
		PolylineOptions polylineOptions = new PolylineOptions();
		for (LatLng point : points) {
			polylineOptions.add(new com.google.android.gms.maps.model.LatLng(point.latitude, point.longitude));
		}
		polylineOptions.color(Color.parseColor(COLOR_CYCLE))
			.width(12);	
		map.addPolyline(polylineOptions);
	}
	
	private void addWalkingPolyline(List<LatLng> points) {
		PolylineOptions polylineOptions = new PolylineOptions();
		for (LatLng point : points) {
			polylineOptions.add(new com.google.android.gms.maps.model.LatLng(point.latitude, point.longitude));
		}
		polylineOptions.color(Color.parseColor(COLOR_WALKING))
			.width(5);
		map.addPolyline(polylineOptions);
	}
	
	private void addAllCycleRacksToMap() {
		for (Feature f : cycleRacksGeoJson.getFeatures()) {
			double[] coords = f.getGeometry().getCoordinates();
			Log.i("Coords", coords[0]+", "+coords[1]);
	        map.addMarker(new MarkerOptions()
	            .title("Bike Rack!")
	            .icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_rack_06))
	            .anchor(0.5f, 0.5f)
//	            .snippet("The most populous city in Scotland.")
	            .position(new com.google.android.gms.maps.model.LatLng(coords[1], coords[0])));
		}
	}

	@Override
	public void onDirectionsDownloadFinished() {
		Log.i("Cycle Directions Download Finished", "");
	}

	@Override
	public void onWalkingDirectionsDownloadFinished() {
		Log.i("Walking Directions Download Finished", "");
	}
	
	
	/**
	 * Private GeoCoding Task Class
	 * 
	 * @author Aidan
	 *
	 */
	private class GeocodingTask extends AsyncTask<String, Integer, List<Address>> {
		
		private String query;
		private boolean from;
				
		public GeocodingTask(boolean from) {
			this.from = from;
			if (from) this.query = fromQuery;
			if (!from) this.query = toQuery;
		}
		
		@Override
		protected List<Address> doInBackground(String... places) {
			Geocoder geocoder = new Geocoder(MapActivity.this, Locale.getDefault());			
			List<Address> results = null;
			
			/*
			 * Sometimes geocoding unexpectedly fails. Make at most
			 * 3 attempts to geocode the place name.
			 */
			try {
				for (int i = 0; results == null || i < 3; i++){
					results = geocoder.getFromLocationName(query+locale, MAX_GEOCODE_RESULTS,
							BOUNDARY_LOWER_LEFT_LATITUDE, BOUNDARY_LOWER_LEFT_LONGITUDE,
							BOUNDARY_UPPER_RIGHT_LATITUDE, BOUNDARY_UPPER_RIGHT_LONGITUDE);
					if (results != null) break;					
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			/* If 'results' is null, GeoCoding failed. In order for
			 * the ListView in the activity to display the "no results"
			 * message, it still needs a list, even if it is empty.
			 */
			return results == null ? new ArrayList<Address>(1) : results;
		}
		
		@Override
		protected void onPostExecute(final List<Address> results) {
			
			/* Set up the list view. */
			if (results.size() > 0) {
				if (from) {
					Log.i("GEOCODE RESULT", "From: "+results.size());
					fromAddress = results.get(0);
				} else {
					Log.i("GEOCODE RESULT", "To: "+results.size());
					toAddress = results.get(0);
				}
				getDirections();
			} else {
				Log.i("GEOCODE - NO RESULT", "");
				Toast.makeText(MapActivity.this, "'"+query+"' not found", Toast.LENGTH_SHORT).show();
			}
		}
	}
	
}